Pelajari praktik terbaik keamanan Python untuk mencegah kerentanan umum. Panduan mendalam ini mencakup manajemen dependensi, serangan injeksi, penanganan data, dan pengkodean aman untuk audiens global.
Praktik Terbaik Keamanan Python: Panduan Komprehensif untuk Pencegahan Kerentanan
Kesederhanaan, fleksibilitas, dan ekosistem pustaka Python yang luas telah menjadikannya kekuatan dominan dalam pengembangan web, ilmu data, kecerdasan buatan, dan automasi. Namun, popularitas global ini menempatkan aplikasi Python tepat di garis bidik para aktor jahat. Sebagai pengembang, tanggung jawab untuk membangun perangkat lunak yang aman dan tangguh menjadi semakin penting. Keamanan bukanlah hal yang dipikirkan belakangan atau fitur yang ditambahkan nanti; ini adalah prinsip dasar yang harus dijalin ke dalam seluruh siklus hidup pengembangan.
Panduan komprehensif ini dirancang untuk audiens global pengembang Python, dari pemula hingga profesional berpengalaman. Kita akan melampaui konsep teoretis dan mendalami praktik terbaik yang praktis dan dapat ditindaklanjuti untuk membantu Anda mengidentifikasi, mencegah, dan memitigasi kerentanan keamanan umum dalam aplikasi Python Anda. Dengan mengadopsi pola pikir yang mengutamakan keamanan, Anda dapat melindungi data Anda, pengguna Anda, dan reputasi organisasi Anda di dunia digital yang semakin kompleks.
Memahami Lanskap Ancaman Python
Sebelum kita dapat bertahan dari ancaman, kita harus memahami apa itu ancaman. Meskipun Python sendiri adalah bahasa yang aman, kerentanan hampir selalu muncul dari cara penggunaannya. Proyek Keamanan Aplikasi Web Terbuka (OWASP) Top 10 menyediakan kerangka kerja yang sangat baik untuk memahami risiko keamanan paling kritis pada aplikasi web, dan hampir semuanya relevan dengan pengembangan Python.
Ancaman umum dalam aplikasi Python meliputi:
- Serangan Injeksi (Injection Attacks): Injeksi SQL, Injeksi Perintah, dan Cross-Site Scripting (XSS) terjadi ketika data yang tidak tepercaya dikirim ke interpreter sebagai bagian dari perintah atau kueri.
- Otentikasi yang Rusak (Broken Authentication): Implementasi otentikasi dan manajemen sesi yang salah dapat memungkinkan penyerang untuk mengkompromikan akun pengguna atau mengambil alih identitas pengguna lain.
- Deserialisasi yang Tidak Aman (Insecure Deserialization): Melakukan deserialisasi data yang tidak tepercaya dapat menyebabkan eksekusi kode jarak jauh, sebuah kerentanan kritis. Modul `pickle` Python adalah penyebab umum.
- Kesalahan Konfigurasi Keamanan (Security Misconfiguration): Kategori luas ini mencakup segalanya mulai dari kredensial default dan pesan kesalahan yang terlalu detail hingga layanan cloud yang dikonfigurasi dengan buruk.
- Komponen Rentan dan Kedaluwarsa: Menggunakan pustaka pihak ketiga dengan kerentanan yang diketahui adalah salah satu risiko yang paling umum dan mudah dieksploitasi.
- Paparan Data Sensitif (Sensitive Data Exposure): Gagal melindungi data sensitif dengan benar, baik saat disimpan (at rest) maupun saat dikirim (in transit), dapat menyebabkan pelanggaran data besar-besaran, melanggar peraturan seperti GDPR, CCPA, dan lainnya di seluruh dunia.
Panduan ini akan memberikan strategi konkret untuk bertahan dari ancaman-ancaman ini dan lebih banyak lagi.
Manajemen Dependensi dan Keamanan Rantai Pasokan
Python Package Index (PyPI) adalah harta karun dengan lebih dari 400.000 paket, yang memungkinkan pengembang membangun aplikasi canggih dengan cepat. Namun, setiap dependensi pihak ketiga yang Anda tambahkan ke proyek Anda adalah vektor serangan potensial baru. Ini dikenal sebagai risiko rantai pasokan. Kerentanan dalam paket yang menjadi dependensi Anda adalah kerentanan dalam aplikasi Anda.
Praktik Terbaik 1: Gunakan Manajer Dependensi yang Tangguh dengan Lock File
File `requirements.txt` sederhana yang dibuat dengan `pip freeze` adalah awal yang baik, tetapi tidak cukup untuk build yang dapat direproduksi dan aman. Alat modern menyediakan lebih banyak kontrol.
- Pipenv: Membuat `Pipfile` untuk mendefinisikan dependensi tingkat atas dan `Pipfile.lock` untuk mengunci versi pasti dari semua dependensi dan sub-dependensi. Ini memastikan bahwa setiap pengembang dan setiap server build menggunakan set paket yang sama persis.
- Poetry: Mirip dengan Pipenv, ia menggunakan file `pyproject.toml` untuk metadata dan dependensi proyek, dan file `poetry.lock` untuk mengunci versi. Poetry banyak dipuji karena resolusi dependensinya yang deterministik.
Mengapa lock file sangat penting? Mereka mencegah situasi di mana versi baru yang berpotensi rentan dari sub-dependensi diinstal secara otomatis, yang dapat merusak aplikasi Anda atau memperkenalkan celah keamanan. Mereka membuat build Anda deterministik dan dapat diaudit.
Praktik Terbaik 2: Pindai Dependensi secara Teratur untuk Kerentanan
Anda tidak dapat melindungi dari kerentanan yang tidak Anda ketahui. Mengintegrasikan pemindaian kerentanan otomatis ke dalam alur kerja Anda sangatlah penting.
- pip-audit: Alat yang dikembangkan oleh Python Packaging Authority (PyPA) yang memindai dependensi proyek Anda terhadap Python Packaging Advisory Database (database penasihat PyPI). Alat ini sederhana dan efektif.
- Safety: Alat baris perintah populer yang memeriksa dependensi yang terinstal untuk kerentanan keamanan yang diketahui.
- Alat Platform Terintegrasi: Layanan seperti Dependabot dari GitHub, Dependency Scanning dari GitLab, dan produk komersial seperti Snyk dan Veracode secara otomatis memindai repositori Anda, mendeteksi dependensi yang rentan, dan bahkan dapat membuat pull request untuk memperbaruinya.
Wawasan yang Dapat Ditindaklanjuti: Integrasikan pemindaian ke dalam pipeline Continuous Integration (CI) Anda. Perintah sederhana seperti `pip-audit -r requirements.txt` dapat ditambahkan ke skrip CI Anda untuk menggagalkan build jika kerentanan baru terdeteksi.
Praktik Terbaik 3: Kunci Dependensi Anda ke Versi Spesifik
Hindari menggunakan penentu versi yang tidak jelas seperti `requests>=2.25.0` atau `requests~=2.25` dalam persyaratan produksi Anda. Meskipun nyaman untuk pengembangan, hal ini menimbulkan ketidakpastian.
SALAH (Tidak Aman): `django>=4.0`
BENAR (Aman): `django==4.1.7`
Ketika Anda mengunci sebuah versi, Anda sedang menguji dan memvalidasi aplikasi Anda terhadap set kode yang diketahui dan spesifik. Ini mencegah perubahan yang tidak terduga yang dapat merusak aplikasi dan memastikan bahwa Anda hanya melakukan peningkatan setelah memiliki kesempatan untuk meninjau kode dan postur keamanan versi baru.
Praktik Terbaik 4: Pertimbangkan Indeks Paket Pribadi
Bagi organisasi, mengandalkan sepenuhnya pada PyPI publik dapat menimbulkan risiko seperti typosquatting, di mana penyerang mengunggah paket berbahaya dengan nama yang mirip dengan paket populer (misalnya, `python-dateutil` vs. `dateutil-python`). Menggunakan repositori paket pribadi seperti JFrog Artifactory, Sonatype Nexus, atau Google Artifact Registry bertindak sebagai proksi yang aman. Anda dapat memeriksa dan menyetujui paket dari PyPI, menyimpannya secara internal, dan memastikan pengembang Anda hanya mengambil dari sumber tepercaya ini.
Mencegah Serangan Injeksi
Serangan injeksi tetap berada di puncak sebagian besar daftar risiko keamanan karena suatu alasan: serangan ini umum, berbahaya, dan dapat menyebabkan kompromi sistem total. Prinsip inti untuk mencegahnya adalah jangan pernah memercayai input pengguna dan memastikan bahwa data yang diberikan pengguna tidak pernah diinterpretasikan langsung sebagai kode.
Injeksi SQL (SQLi)
SQLi terjadi ketika seorang penyerang dapat memanipulasi kueri SQL suatu aplikasi. Hal ini dapat menyebabkan akses, modifikasi, atau penghapusan data yang tidak sah.
Contoh RENTAN (Jangan gunakan):
Kode ini menggunakan format string untuk membangun kueri. Jika `user_id` adalah sesuatu seperti `"105 OR 1=1"`, kueri tersebut akan mengembalikan semua pengguna.
import sqlite3
conn = sqlite3.connect('example.db')
cursor = conn.cursor()
user_id = input("Enter user ID: ")
# BERBAHAYA: Memformat input pengguna langsung ke dalam kueri
query = f"SELECT * FROM users WHERE id = {user_id}"
cursor.execute(query)
Solusi AMAN: Kueri Berparameter (Query Binding)
Driver basis data menangani substitusi nilai yang aman, memperlakukan input pengguna secara ketat sebagai data, bukan sebagai bagian dari perintah SQL.
# AMAN: Menggunakan placeholder (?) dan meneruskan data sebagai tuple
query = "SELECT * FROM users WHERE id = ?"
cursor.execute(query, (user_id,))
Sebagai alternatif, menggunakan Object-Relational Mapper (ORM) seperti SQLAlchemy atau Django ORM mengabstraksi SQL mentah, menyediakan pertahanan bawaan yang kuat terhadap SQLi.
# AMAN dengan SQLAlchemy
from sqlalchemy.orm import sessionmaker
# ... (setup)
session = Session()
user = session.query(User).filter(User.id == user_id).first()
Injeksi Perintah
Kerentanan ini memungkinkan penyerang untuk mengeksekusi perintah sewenang-wenang pada sistem operasi host. Ini biasanya terjadi ketika sebuah aplikasi meneruskan input pengguna yang tidak aman ke shell sistem.
Contoh RENTAN (Jangan gunakan):
Menggunakan `shell=True` dengan `subprocess.run()` sangat berbahaya jika perintah berisi data yang dikontrol pengguna. Penyerang dapat meneruskan `"; rm -rf /"` sebagai bagian dari nama file.
import subprocess
filename = input("Enter filename to list details: ")
# BERBAHAYA: shell=True menginterpretasikan seluruh string, termasuk perintah berbahaya
subprocess.run(f"ls -l {filename}", shell=True)
Solusi AMAN: Daftar Argumen
Pendekatan paling aman adalah menghindari `shell=True` dan meneruskan argumen perintah sebagai daftar. Dengan cara ini, sistem operasi menerima argumen secara terpisah dan tidak akan menginterpretasikan metakarakter dalam input.
# AMAN: Meneruskan argumen sebagai daftar. namafile diperlakukan sebagai satu argumen.
subprocess.run(["ls", "-l", filename])
Jika Anda benar-benar harus membuat perintah shell dari beberapa bagian, gunakan `shlex.quote()` untuk melakukan escape pada karakter khusus apa pun dalam input pengguna, membuatnya aman untuk interpretasi shell.
Cross-Site Scripting (XSS)
Kerentanan XSS terjadi ketika sebuah aplikasi menyertakan data yang tidak tepercaya di halaman web tanpa validasi atau escaping yang tepat. Hal ini memungkinkan penyerang untuk mengeksekusi skrip di browser korban, yang dapat digunakan untuk membajak sesi pengguna, merusak situs web, atau mengalihkan pengguna ke situs berbahaya.
Solusi: Escaping Output yang Sadar Konteks
Kerangka kerja web Python modern adalah sekutu terbesar Anda di sini. Mesin templat seperti Jinja2 (digunakan oleh Flask) dan Django Templates melakukan auto-escaping secara default. Ini berarti setiap data yang dirender dalam templat HTML akan memiliki karakter seperti `<`, `>`, dan `&` yang diubah menjadi entitas HTML yang aman (`<`, `>`, `&`).
Contoh (Jinja2):
Jika seorang pengguna mengirimkan nama mereka sebagai `""`, Jinja2 akan merendernya dengan aman.
from flask import Flask, render_template_string
app = Flask(__name__)
@app.route('/greet')
def greet():
# Input berbahaya dari pengguna
user_name = ""
# Jinja2 akan secara otomatis melakukan escape
template = "Hello, {{ name }}!
"
return render_template_string(template, name=user_name)
# HTML yang dirender akan menjadi:
# Hello, <script>alert('XSS')</script>!
# Skrip tidak akan dieksekusi.
Wawasan yang Dapat Ditindaklanjuti: Jangan pernah menonaktifkan auto-escaping kecuali Anda memiliki alasan yang sangat kuat dan sepenuhnya memahami risikonya. Jika Anda harus merender HTML mentah, gunakan pustaka seperti `bleach` untuk membersihkannya terlebih dahulu dengan menghapus semua tag dan atribut HTML kecuali subset yang diketahui aman.
Penanganan dan Penyimpanan Data yang Aman
Melindungi data pengguna adalah kewajiban hukum dan etis. Peraturan privasi data global seperti GDPR Uni Eropa, LGPD Brasil, dan CCPA California memberlakukan persyaratan ketat dan hukuman berat bagi yang tidak patuh.
Praktik Terbaik 1: Jangan Pernah Menyimpan Kata Sandi dalam Teks Biasa
Ini adalah dosa utama dalam keamanan. Menyimpan kata sandi sebagai teks biasa, atau bahkan dengan algoritma hashing usang seperti MD5 atau SHA1, sama sekali tidak aman. Serangan modern dapat memecahkan hash ini dalam hitungan detik.
Solusi: Gunakan Algoritma Hashing yang Kuat, Di-Salt, dan Adaptif
- Kuat: Algoritma harus tahan terhadap tabrakan (collision).
- Di-Salt (Salted): Salt acak yang unik ditambahkan ke setiap kata sandi sebelum di-hash. Ini memastikan bahwa dua kata sandi yang identik akan memiliki hash yang berbeda, menggagalkan serangan rainbow table.
- Adaptif: Biaya komputasi algoritma dapat ditingkatkan seiring waktu untuk mengimbangi perangkat keras yang lebih cepat, membuat serangan brute-force lebih sulit.
Pilihan terbaik di Python adalah Bcrypt dan Argon2. Pustaka `argon2-cffi` dan `bcrypt` membuatnya mudah.
Contoh dengan bcrypt:
import bcrypt
password = b"SuperSecretP@ssword123"
# Melakukan hashing kata sandi (salt dibuat dan disertakan secara otomatis)
hashed = bcrypt.hashpw(password, bcrypt.gensalt())
# ... Simpan 'hashed' di basis data Anda ...
# Memeriksa kata sandi
user_entered_password = b"SuperSecretP@ssword123"
if bcrypt.checkpw(user_entered_password, hashed):
print("Kata sandi cocok!")
else:
print("Kata sandi salah.")
Praktik Terbaik 2: Kelola Rahasia dengan Aman
Kode sumber Anda tidak boleh berisi informasi sensitif seperti kunci API, kredensial basis data, atau kunci enkripsi. Menyimpan rahasia ke sistem kontrol versi seperti Git adalah resep bencana, karena dapat dengan mudah ditemukan.
Solusi: Eksternalisasi Konfigurasi
- Variabel Lingkungan (Environment Variables): Ini adalah metode standar dan paling portabel. Aplikasi Anda membaca rahasia dari lingkungan tempat ia berjalan. Untuk pengembangan lokal, file `.env` dapat digunakan dengan pustaka `python-dotenv` untuk mensimulasikannya. File `.env` tidak boleh disimpan ke sistem kontrol versi (tambahkan ke `.gitignore` Anda).
- Alat Manajemen Rahasia: Untuk lingkungan produksi, terutama di cloud, menggunakan manajer rahasia khusus adalah pendekatan yang paling aman. Layanan seperti AWS Secrets Manager, Google Cloud Secret Manager, atau HashiCorp Vault menyediakan penyimpanan terpusat yang terenkripsi dengan kontrol akses terperinci dan pencatatan audit.
Praktik Terbaik 3: Bersihkan Log
Log sangat berharga untuk debugging dan pemantauan, tetapi juga bisa menjadi sumber kebocoran data. Pastikan konfigurasi logging Anda tidak secara tidak sengaja merekam informasi sensitif seperti kata sandi, token sesi, kunci API, atau informasi identitas pribadi (PII).
Wawasan yang Dapat Ditindaklanjuti: Terapkan filter atau formatter logging kustom yang secara otomatis menyunting atau menutupi bidang dengan kunci sensitif yang diketahui (misalnya, 'password', 'credit_card', 'ssn').
Praktik Pengkodean Aman dalam Python
Banyak kerentanan dapat dicegah dengan mengadopsi kebiasaan aman selama proses pengkodean itu sendiri.
Praktik Terbaik 1: Validasi Semua Input
Seperti yang disebutkan sebelumnya, jangan pernah memercayai input pengguna. Ini berlaku untuk data yang berasal dari formulir web, klien API, file, dan bahkan sistem lain dalam infrastruktur Anda. Validasi input memastikan bahwa data sesuai dengan format, tipe, panjang, dan rentang yang diharapkan sebelum diproses.
Menggunakan pustaka validasi data seperti Pydantic sangat disarankan. Pustaka ini memungkinkan Anda untuk mendefinisikan model data dengan petunjuk tipe (type hints), dan secara otomatis akan mengurai, memvalidasi, dan memberikan pesan kesalahan yang jelas untuk data yang masuk.
Contoh dengan Pydantic:
from pydantic import BaseModel, EmailStr, constr
class UserRegistration(BaseModel):
email: EmailStr # Memvalidasi format email yang benar
username: constr(min_length=3, max_length=50) # Membatasi panjang string
age: int
try:
# Data dari permintaan API
raw_data = {'email': 'test@example.com', 'username': 'usr', 'age': 25}
user = UserRegistration(**raw_data)
print("Validasi berhasil!")
except ValueError as e:
print(f"Validasi gagal: {e}")
Praktik Terbaik 2: Hindari Deserialisasi yang Tidak Aman
Deserialisasi adalah proses mengubah aliran data (seperti string atau byte) kembali menjadi objek. Modul `pickle` Python terkenal tidak aman karena dapat dimanipulasi untuk mengeksekusi kode sewenang-wenang saat melakukan deserialisasi payload yang dibuat dengan jahat. Jangan pernah melakukan unpickle pada data dari sumber yang tidak tepercaya atau tidak terotentikasi.
Solusi: Gunakan Format Serialisasi yang Aman
Untuk pertukaran data, lebih baik gunakan format yang lebih aman dan dapat dibaca manusia seperti JSON. JSON hanya mendukung tipe data sederhana (string, angka, boolean, daftar, kamus), sehingga tidak dapat digunakan untuk mengeksekusi kode. Jika Anda perlu melakukan serialisasi objek Python yang kompleks, Anda harus memastikan sumbernya tepercaya atau menggunakan pustaka serialisasi yang lebih aman yang dirancang dengan mempertimbangkan keamanan.
Praktik Terbaik 3: Tangani Unggahan File dan Path dengan Aman
Mengizinkan pengguna mengunggah file atau mengontrol path file dapat menyebabkan dua kerentanan utama:
- Unggahan File Tidak Terbatas: Seorang penyerang dapat mengunggah file yang dapat dieksekusi (misalnya, skrip `.php` atau `.sh`) ke server Anda dan kemudian mengeksekusinya, yang mengarah ke kompromi penuh.
- Path Traversal: Seorang penyerang dapat memberikan input seperti `../../etc/passwd` untuk mencoba membaca atau menulis file di luar direktori yang dimaksud.
Solusi:
- Validasi Tipe dan Nama File: Gunakan daftar putih (whitelist) ekstensi file dan tipe MIME yang diizinkan. Jangan pernah hanya mengandalkan header `Content-Type`, karena dapat dipalsukan.
- Bersihkan Nama File: Hapus pemisah direktori (`/`, `\`) dan karakter khusus (`..`) dari nama file yang diberikan pengguna. Praktik yang baik adalah membuat nama file acak yang baru untuk file yang disimpan.
- Simpan Unggahan di Luar Root Web: Simpan file yang diunggah di direktori yang tidak disajikan langsung oleh server web. Akses file tersebut melalui skrip yang memeriksa otentikasi dan otorisasi terlebih dahulu.
- Gunakan `os.path.basename` dan penggabungan path yang aman: Saat bekerja dengan nama file yang diberikan pengguna, gunakan fungsi yang mencegah traversal.
Peralatan untuk Siklus Hidup Pengembangan yang Aman
Memeriksa setiap potensi kerentanan secara manual adalah hal yang mustahil. Mengintegrasikan alat keamanan otomatis ke dalam alur kerja pengembangan Anda sangat penting untuk membangun aplikasi yang aman dalam skala besar.
Static Application Security Testing (SAST)
Alat SAST, juga dikenal sebagai pengujian "white-box", menganalisis kode sumber Anda tanpa menjalankannya untuk menemukan potensi cacat keamanan. Alat ini sangat baik untuk menangkap kesalahan umum di awal proses pengembangan.
Untuk Python, alat SAST open-source terkemuka adalah Bandit. Ia bekerja dengan mengurai kode Anda menjadi Abstract Syntax Tree (AST) dan menjalankan plugin untuk menemukan masalah keamanan umum.
Contoh Penggunaan:
# Instal bandit
$ pip install bandit
# Jalankan pada folder proyek Anda
$ bandit -r your_project/
Integrasikan Bandit ke dalam pipeline CI Anda untuk memindai setiap commit atau pull request secara otomatis.
Dynamic Application Security Testing (DAST)
Alat DAST, atau pengujian "black-box", menganalisis aplikasi Anda saat sedang berjalan. Mereka tidak memiliki akses ke kode sumber; sebaliknya, mereka menyelidiki aplikasi dari luar, seperti yang dilakukan penyerang, untuk menemukan kerentanan seperti XSS, SQLi, dan kesalahan konfigurasi keamanan.
Alat DAST open-source yang populer dan kuat adalah OWASP Zed Attack Proxy (ZAP). Alat ini dapat digunakan untuk memindai lalu lintas secara pasif atau secara aktif menyerang aplikasi Anda untuk menemukan cacat.
Interactive Application Security Testing (IAST)
IAST adalah kategori peralatan yang lebih baru yang menggabungkan elemen SAST dan DAST. IAST menggunakan instrumentasi untuk memantau aplikasi dari dalam saat berjalan, memungkinkannya mendeteksi bagaimana input pengguna mengalir melalui kode dan mengidentifikasi kerentanan dengan akurasi tinggi dan positif palsu yang rendah.
Kesimpulan: Membangun Budaya Keamanan
Menulis kode Python yang aman bukan tentang menghafal daftar periksa kerentanan. Ini tentang menumbuhkan pola pikir di mana keamanan menjadi pertimbangan utama di setiap tahap pengembangan. Ini adalah proses berkelanjutan untuk belajar, menerapkan praktik terbaik, dan memanfaatkan automasi untuk membangun aplikasi yang tangguh dan dapat dipercaya.
Mari kita rekap poin-poin penting untuk tim pengembangan global Anda:
- Amankan Rantai Pasokan Anda: Gunakan lock file, pindai dependensi Anda secara teratur, dan kunci versi untuk mencegah kerentanan dari paket pihak ketiga.
- Cegah Injeksi: Selalu perlakukan input pengguna sebagai data yang tidak tepercaya. Gunakan kueri berparameter, panggilan subprocess yang aman, dan auto-escaping sadar konteks yang disediakan oleh kerangka kerja modern.
- Lindungi Data: Gunakan hashing kata sandi yang kuat dan di-salt. Eksternalisasi rahasia menggunakan variabel lingkungan atau manajer rahasia. Validasi dan bersihkan semua data yang masuk ke sistem Anda.
- Adopsi Kebiasaan Aman: Hindari modul berbahaya seperti `pickle` dengan data yang tidak tepercaya, tangani path file dengan hati-hati, dan validasi setiap input.
- Otomatiskan Keamanan: Integrasikan alat SAST dan DAST seperti Bandit dan OWASP ZAP ke dalam pipeline CI/CD Anda untuk menangkap kerentanan sebelum mencapai produksi.
Dengan menanamkan prinsip-prinsip ini ke dalam alur kerja Anda, Anda beralih dari postur keamanan reaktif ke postur proaktif. Anda membangun aplikasi yang tidak hanya fungsional dan efisien, tetapi juga tangguh dan aman, mendapatkan kepercayaan pengguna Anda di seluruh dunia.